package com.xbongbong.formula.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.xbongbong.formula.enums.MathEnum;
import com.xbongbong.formula.qlexpress.operator.advanced.OperatorUuid;
import com.xbongbong.formula.qlexpress.operator.date.OperatorAddDay;
import com.xbongbong.formula.qlexpress.operator.date.OperatorAddMonth;
import com.xbongbong.formula.qlexpress.operator.date.OperatorAddYear;
import com.xbongbong.formula.qlexpress.operator.date.OperatorDate;
import com.xbongbong.formula.qlexpress.operator.date.OperatorDateDelta;
import com.xbongbong.formula.qlexpress.operator.date.OperatorDay;
import com.xbongbong.formula.qlexpress.operator.date.OperatorDays;
import com.xbongbong.formula.qlexpress.operator.date.OperatorDays360;
import com.xbongbong.formula.qlexpress.operator.date.OperatorHour;
import com.xbongbong.formula.qlexpress.operator.date.OperatorIsoWeekNum;
import com.xbongbong.formula.qlexpress.operator.date.OperatorMinute;
import com.xbongbong.formula.qlexpress.operator.date.OperatorMinutes;
import com.xbongbong.formula.qlexpress.operator.date.OperatorMonth;
import com.xbongbong.formula.qlexpress.operator.date.OperatorNow;
import com.xbongbong.formula.qlexpress.operator.date.OperatorOneYearRangDay;
import com.xbongbong.formula.qlexpress.operator.date.OperatorQuarter;
import com.xbongbong.formula.qlexpress.operator.date.OperatorSecond;
import com.xbongbong.formula.qlexpress.operator.date.OperatorSystemTime;
import com.xbongbong.formula.qlexpress.operator.date.OperatorSystemTimeMs;
import com.xbongbong.formula.qlexpress.operator.date.OperatorTime;
import com.xbongbong.formula.qlexpress.operator.date.OperatorTimestamp;
import com.xbongbong.formula.qlexpress.operator.date.OperatorToday;
import com.xbongbong.formula.qlexpress.operator.date.OperatorWeekNum;
import com.xbongbong.formula.qlexpress.operator.date.OperatorWeekday;
import com.xbongbong.formula.qlexpress.operator.date.OperatorYear;
import com.xbongbong.formula.qlexpress.operator.date.OperatorYears;
import com.xbongbong.formula.qlexpress.operator.logic.OperatorAndOr;
import com.xbongbong.formula.qlexpress.operator.logic.OperatorIf;
import com.xbongbong.formula.qlexpress.operator.logic.OperatorIsEmpty;
import com.xbongbong.formula.qlexpress.operator.logic.OperatorNot;
import com.xbongbong.formula.qlexpress.operator.logic.OperatorTrueFalse;
import com.xbongbong.formula.qlexpress.operator.logic.OperatorXor;
import com.xbongbong.formula.qlexpress.operator.math.OperatorAbs;
import com.xbongbong.formula.qlexpress.operator.math.OperatorAdd;
import com.xbongbong.formula.qlexpress.operator.math.OperatorAverage;
import com.xbongbong.formula.qlexpress.operator.math.OperatorCeiling;
import com.xbongbong.formula.qlexpress.operator.math.OperatorCount;
import com.xbongbong.formula.qlexpress.operator.math.OperatorCountIf;
import com.xbongbong.formula.qlexpress.operator.math.OperatorDivide;
import com.xbongbong.formula.qlexpress.operator.math.OperatorFloor;
import com.xbongbong.formula.qlexpress.operator.math.OperatorInt;
import com.xbongbong.formula.qlexpress.operator.math.OperatorLargeSmall;
import com.xbongbong.formula.qlexpress.operator.math.OperatorLog;
import com.xbongbong.formula.qlexpress.operator.math.OperatorMinMax;
import com.xbongbong.formula.qlexpress.operator.math.OperatorMod;
import com.xbongbong.formula.qlexpress.operator.math.OperatorMultiply;
import com.xbongbong.formula.qlexpress.operator.math.OperatorPower;
import com.xbongbong.formula.qlexpress.operator.math.OperatorProduct;
import com.xbongbong.formula.qlexpress.operator.math.OperatorRand;
import com.xbongbong.formula.qlexpress.operator.math.OperatorRound;
import com.xbongbong.formula.qlexpress.operator.math.OperatorRoundDown;
import com.xbongbong.formula.qlexpress.operator.math.OperatorRoundUp;
import com.xbongbong.formula.qlexpress.operator.math.OperatorSubtract;
import com.xbongbong.formula.qlexpress.operator.math.OperatorSum;
import com.xbongbong.formula.qlexpress.operator.math.OperatorSumIf;
import com.xbongbong.formula.qlexpress.operator.math.OperatorSumProduct;
import com.xbongbong.formula.qlexpress.operator.math.OperatorUpper;
import com.xbongbong.formula.qlexpress.operator.math.OperatorUpperMoney;
import com.xbongbong.formula.qlexpress.operator.text.OperatorBase64;
import com.xbongbong.formula.qlexpress.operator.text.OperatorChar;
import com.xbongbong.formula.qlexpress.operator.text.OperatorConcatenate;
import com.xbongbong.formula.qlexpress.operator.text.OperatorExact;
import com.xbongbong.formula.qlexpress.operator.text.OperatorFixed;
import com.xbongbong.formula.qlexpress.operator.text.OperatorJoin;
import com.xbongbong.formula.qlexpress.operator.text.OperatorLeft;
import com.xbongbong.formula.qlexpress.operator.text.OperatorLen;
import com.xbongbong.formula.qlexpress.operator.text.OperatorLower;
import com.xbongbong.formula.qlexpress.operator.text.OperatorMd5;
import com.xbongbong.formula.qlexpress.operator.text.OperatorMid;
import com.xbongbong.formula.qlexpress.operator.text.OperatorReplace;
import com.xbongbong.formula.qlexpress.operator.text.OperatorRept;
import com.xbongbong.formula.qlexpress.operator.text.OperatorRight;
import com.xbongbong.formula.qlexpress.operator.text.OperatorSearch;
import com.xbongbong.formula.qlexpress.operator.text.OperatorSha256;
import com.xbongbong.formula.qlexpress.operator.text.OperatorSplit;
import com.xbongbong.formula.qlexpress.operator.text.OperatorSqrt;
import com.xbongbong.formula.qlexpress.operator.text.OperatorText;
import com.xbongbong.formula.qlexpress.operator.text.OperatorTrim;
import com.xbongbong.formula.qlexpress.operator.text.OperatorValue;
import com.xbongbong.formula.service.FunctionService;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.RegexUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.constant.BusinessConstant;
import com.xbongbong.pro.constant.I18nStringConstant;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.ProErrorCodeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Objects;

/**
 * @author: wufeng
 * @date: 2018/7/4 13:43
 * @desrcption:
 */
@Service("functionService")
public class FunctionServiceQlExpressImpl implements FunctionService {

   private final static Logger LOGGER = LoggerFactory.getLogger(FunctionServiceQlExpressImpl.class);

    private ExpressRunner runner;

    public FunctionServiceQlExpressImpl() {
        init();
    }

    /**
     * 初始化方法，1.主要过程为实例化ExpressRunner, 2.将自建函数注册到ExpressRunner
     * @author kaka
     * @date: 2018/7/12
     */
    public void init() {
        runner = new ExpressRunner();
        addFunction(runner);
        addOperator(runner);
    }
    @Override
    public Object expressRunValue(String value) throws XbbException {
        Object r = false;
        // 获取上下文
        DefaultContext<String, Object> context = new DefaultContext<>();
        try {
            value = value.replaceAll("UUID\\(\\)", "GUID()");
            r = runner.execute(value, context, null, true, false);
        } catch (Exception e){
            LOGGER.error("functionServiceQlExpressImpl.expressRunValue 解析失败：公式为：" + value + ":", e);
            throw new XbbException(ProErrorCodeEnum.API_ERROR_260018, String.format(I18nMessageUtil.getMessage(ProErrorCodeEnum.API_ERROR_260018.getMsg()), value));
        }
        return r;
    }


    @Override
    public Object expressRun(Object formula, Map<String, Object> map) throws XbbException {
        if (Objects.isNull(formula) || StringUtil.isEmpty(formula.toString())) {
            return null;
        }
        Object r = false;
        String value = formula.toString();
        if (value.contains(BusinessConstant.PROVINCE)) {
            try {
                JSONObject jsonObject = JSON.parseObject(value);
                return jsonObject;
            } catch (Exception e) {
                LOGGER.error("functionServiceQlExpressImpl.expressRun解析失败：公式为：" + value + ":", e);
            }
        }
        String regex = "\\{self.|_widget_|_formula_|dept.|user.|\\}";
        String[] attrArray = RegexUtil.getFormulaAttr(value);
        for (int i = 0; i < attrArray.length; i++) {
            String attr = attrArray[i];
            String key = attr.replaceAll(regex, "");
            Object val =  map.get(key);
            if (Objects.isNull(val)) {
                return null;
            }
            value = value.replace(attr, val.toString());
        }
        try {
            r = expressRunValue(value);
        } catch (XbbException e) {
            LOGGER.error("functionServiceQlExpressImpl.expressRun解析失败：公式为：" + value + ":", e);
            throw e;
        } catch (Exception e){
           LOGGER.error("functionServiceQlExpressImpl.expressRun解析失败：公式为：" + value + ":", e);
           throw new XbbException(ProErrorCodeEnum.API_ERROR_260018, String.format(I18nMessageUtil.getMessage(ProErrorCodeEnum.API_ERROR_260018.getMsg()), value));
        }
        return r;
    }

    /**
     * 注册自建函数
     * @param runner 阿里QLExpress的ExpressRunner实例
     */
    public void addFunction(ExpressRunner runner){
        if(runner == null) {
            throw new IllegalArgumentException(String.format(I18nMessageUtil.getMessage(I18nStringConstant.CANNOT_BE_EMPTY), "runner"));
        }
        /** 数学函数 29个*/
        runner.addFunction(MathEnum.AVERAGE.getFormula(), new OperatorAverage(MathEnum.AVERAGE.getFormula()));
        runner.addFunction(MathEnum.COUNT.getFormula(), new OperatorCount(MathEnum.COUNT.getFormula()));
        runner.addFunction(MathEnum.COUNTIF.getFormula(), new OperatorCountIf(MathEnum.COUNTIF.getFormula()));
        runner.addFunction(MathEnum.MAX.getFormula(), new OperatorMinMax(MathEnum.MAX.getFormula()));
        runner.addFunction(MathEnum.MIN.getFormula(), new OperatorMinMax(MathEnum.MIN.getFormula()));
        runner.addFunction(MathEnum.LARGE.getFormula(), new OperatorLargeSmall(MathEnum.LARGE.getFormula()));
        runner.addFunction(MathEnum.SMALL.getFormula(), new OperatorLargeSmall(MathEnum.SMALL.getFormula()));
        runner.addFunction(MathEnum.ABS.getFormula(), new OperatorAbs(MathEnum.ABS.getFormula()));
        runner.addFunction(MathEnum.ROUND.getFormula(), new OperatorRound(MathEnum.ROUND.getFormula()));
        runner.addFunction(MathEnum.ROUNDUP.getFormula(), new OperatorRoundUp(MathEnum.ROUNDUP.getFormula()));
        runner.addFunction(MathEnum.ROUNDDOWN.getFormula(), new OperatorRoundDown(MathEnum.ROUNDDOWN.getFormula()));
        runner.addFunction(MathEnum.CEILING.getFormula(), new OperatorCeiling(MathEnum.CEILING.getFormula()));
        runner.addFunction(MathEnum.FLOOR.getFormula(), new OperatorFloor(MathEnum.FLOOR.getFormula()));
        runner.addFunction(MathEnum.INT.getFormula(), new OperatorInt(MathEnum.INT.getFormula()));
        runner.addFunction(MathEnum.LOG.getFormula(), new OperatorLog(MathEnum.LOG.getFormula()));
        runner.addFunction(MathEnum.MOD.getFormula(), new OperatorMod(MathEnum.MOD.getFormula()));
        runner.addFunction(MathEnum.POWER.getFormula(), new OperatorPower(MathEnum.POWER.getFormula()));
        runner.addFunction(MathEnum.PRODUCT.getFormula(), new OperatorProduct(MathEnum.PRODUCT.getFormula()));
        runner.addFunction(MathEnum.SQRT.getFormula(), new OperatorSqrt(MathEnum.SQRT.getFormula()));
        runner.addFunction(MathEnum.SUM.getFormula(), new OperatorSum(MathEnum.SUM.getFormula()));
        runner.addFunction(MathEnum.SUMIF.getFormula(), new OperatorSumIf(MathEnum.SUMIF.getFormula()));
        runner.addFunction(MathEnum.SUMPRODUCT.getFormula(), new OperatorSumProduct(MathEnum.SUMPRODUCT.getFormula()));
        runner.addFunction(MathEnum.FIXED.getFormula(), new OperatorFixed(MathEnum.FIXED.getFormula()));
        runner.addFunction(MathEnum.RAND.getFormula(), new OperatorRand(MathEnum.RAND.getFormula()));
        runner.addFunction(MathEnum.UPPERMONEY.getFormula(), new OperatorUpperMoney(MathEnum.UPPERMONEY.getFormula()));
        // 精确计算
        runner.addFunction(MathEnum.ADD.getFormula(), new OperatorAdd(MathEnum.ADD.getFormula()));
        runner.addFunction(MathEnum.SUBTRACT.getFormula(), new OperatorSubtract(MathEnum.SUBTRACT.getFormula()));
        runner.addFunction(MathEnum.MULTIPLY.getFormula(), new OperatorMultiply(MathEnum.MULTIPLY.getFormula()));
        runner.addFunction(MathEnum.DIVIDE.getFormula(), new OperatorDivide(MathEnum.DIVIDE.getFormula()));
        /**文本函数 16*/
        runner.addFunction(MathEnum.CONCATENATE.getFormula(), new OperatorConcatenate(MathEnum.CONCATENATE.getFormula()));
        runner.addFunction(MathEnum.CHAR.getFormula(), new OperatorChar(MathEnum.CHAR.getFormula()));
        runner.addFunction(MathEnum.EXACT.getFormula(), new OperatorExact(MathEnum.EXACT.getFormula()));
        runner.addFunction(MathEnum.LEFT.getFormula(), new OperatorLeft(MathEnum.LEFT.getFormula()));
        runner.addFunction(MathEnum.LEN.getFormula(), new OperatorLen(MathEnum.LEN.getFormula()));
        runner.addFunction(MathEnum.LOWER.getFormula(), new OperatorLower(MathEnum.LOWER.getFormula()));
        runner.addFunction(MathEnum.REPLACE.getFormula(), new OperatorReplace(MathEnum.REPLACE.getFormula()));
        runner.addFunction(MathEnum.REPT.getFormula(), new OperatorRept(MathEnum.REPT.getFormula()));
        runner.addFunction(MathEnum.RIGHT.getFormula(), new OperatorRight(MathEnum.RIGHT.getFormula()));
        runner.addFunction(MathEnum.SEARCH.getFormula(), new OperatorSearch(MathEnum.SEARCH.getFormula()));
        runner.addFunction(MathEnum.SPLIT.getFormula(), new OperatorSplit(MathEnum.SPLIT.getFormula()));
        runner.addFunction(MathEnum.TRIM.getFormula(), new OperatorTrim(MathEnum.TRIM.getFormula()));
        runner.addFunction(MathEnum.MID.getFormula(), new OperatorMid(MathEnum.MID.getFormula()));
        runner.addFunction(MathEnum.UPPER.getFormula(), new OperatorUpper(MathEnum.UPPER.getFormula()));
        runner.addFunction(MathEnum.TEXT.getFormula(), new OperatorText(MathEnum.TEXT.getFormula()));
        runner.addFunction(MathEnum.VALUE.getFormula(), new OperatorValue(MathEnum.VALUE.getFormula()));
        runner.addFunction(MathEnum.JOIN.getFormula(), new OperatorJoin(MathEnum.JOIN.getFormula()));
        runner.addFunction(MathEnum.BASE64.getFormula(), new OperatorBase64(MathEnum.BASE64.getFormula()));
        runner.addFunction(MathEnum.SHA256.getFormula(), new OperatorSha256(MathEnum.SHA256.getFormula()));
        runner.addFunction(MathEnum.MD5.getFormula(), new OperatorMd5(MathEnum.MD5.getFormula()));
        /** 逻辑函数 8个**/
        runner.addFunction(MathEnum.AND.getFormula(), new OperatorAndOr(MathEnum.AND.getFormula()));
        runner.addFunction(MathEnum.OR.getFormula(), new OperatorAndOr(MathEnum.OR.getFormula()));
        runner.addFunction(MathEnum.NOT.getFormula(), new OperatorNot(MathEnum.NOT.getFormula()));
        runner.addFunction(MathEnum.FALSE.getFormula(), new OperatorTrueFalse(MathEnum.FALSE.getFormula()));
        runner.addFunction(MathEnum.TRUE.getFormula(), new OperatorTrueFalse(MathEnum.TRUE.getFormula()));
        runner.addFunction(MathEnum.IF.getFormula(), new OperatorIf(MathEnum.IF.getFormula()));
        runner.addFunction(MathEnum.XOR.getFormula(), new OperatorXor(MathEnum.XOR.getFormula()));
        runner.addFunction(MathEnum.ISEMPTY.getFormula(), new OperatorIsEmpty(MathEnum.ISEMPTY.getFormula()));
        /**日期函数 */
        runner.addFunction(MathEnum.YEARS.getFormula(), new OperatorYears(MathEnum.YEARS.getFormula()));
        runner.addFunction(MathEnum.DAYS.getFormula(), new OperatorDays(MathEnum.DAYS.getFormula()));
        runner.addFunction(MathEnum.HOURS.getFormula(), new OperatorYears(MathEnum.HOURS.getFormula()));
        runner.addFunction(MathEnum.MINUTES.getFormula(), new OperatorMinutes(MathEnum.MINUTES.getFormula()));
        runner.addFunction(MathEnum.NOW.getFormula(), new OperatorNow(MathEnum.NOW.getFormula()));
        runner.addFunction(MathEnum.ADDDAY.getFormula(), new OperatorAddDay(MathEnum.ADDDAY.getFormula()));
        runner.addFunction(MathEnum.ADDMONTH.getFormula(), new OperatorAddMonth(MathEnum.ADDMONTH.getFormula()));
        runner.addFunction(MathEnum.ADDYEAR.getFormula(), new OperatorAddYear(MathEnum.ADDYEAR.getFormula()));
        runner.addFunction(MathEnum.DATE.getFormula(), new OperatorDate(MathEnum.DATE.getFormula()));
        runner.addFunction(MathEnum.DATEDELTA.getFormula(), new OperatorDateDelta(MathEnum.DATEDELTA.getFormula()));
        runner.addFunction(MathEnum.YEAR.getFormula(), new OperatorYear(MathEnum.YEAR.getFormula()));
        runner.addFunction(MathEnum.MONTH.getFormula(), new OperatorMonth(MathEnum.MONTH.getFormula()));
        runner.addFunction(MathEnum.DAY.getFormula(), new OperatorDay(MathEnum.DAY.getFormula()));
        runner.addFunction(MathEnum.HOUR.getFormula(), new OperatorHour(MathEnum.HOUR.getFormula()));
        runner.addFunction(MathEnum.MINUTE.getFormula(), new OperatorMinute(MathEnum.MINUTE.getFormula()));
        runner.addFunction(MathEnum.QUARTER.getFormula(), new OperatorQuarter(MathEnum.QUARTER.getFormula()));
        runner.addFunction(MathEnum.TODAY.getFormula(), new OperatorToday(MathEnum.TODAY.getFormula()));
        runner.addFunction(MathEnum.WEEKDAY.getFormula(), new OperatorWeekday(MathEnum.WEEKDAY.getFormula()));
        runner.addFunction(MathEnum.WEEKNUM.getFormula(), new OperatorWeekNum(MathEnum.WEEKNUM.getFormula()));
        runner.addFunction(MathEnum.TIMESTAMP.getFormula(), new OperatorTimestamp(MathEnum.TIMESTAMP.getFormula()));
        runner.addFunction(MathEnum.TIME.getFormula(), new OperatorTime(MathEnum.TIME.getFormula()));
        runner.addFunction(MathEnum.SYSTIME.getFormula(), new OperatorSystemTime(MathEnum.SYSTIME.getFormula()));
        runner.addFunction(MathEnum.SYSTIMEMS.getFormula(), new OperatorSystemTimeMs(MathEnum.SYSTIMEMS.getFormula()));
        runner.addFunction(MathEnum.SECOND.getFormula(), new OperatorSecond(MathEnum.SECOND.getFormula()));
        runner.addFunction(MathEnum.DAYS360.getFormula(), new OperatorDays360(MathEnum.DAYS360.getFormula()));
        runner.addFunction(MathEnum.ONEYEARRANGDAY.getFormula(), new OperatorOneYearRangDay(MathEnum.ONEYEARRANGDAY.getFormula()));
        runner.addFunction(MathEnum.ISOWEEKNUM.getFormula(), new OperatorIsoWeekNum(MathEnum.ISOWEEKNUM.getFormula()));
        // 高级函数
        runner.addFunction(MathEnum.GUID.getFormula(), new OperatorUuid(MathEnum.GUID.getFormula()));

    }

    public void addOperator(ExpressRunner runner) {
        if (runner == null) {
            throw new IllegalArgumentException("runner 参数不能为空");
        }
        try {
            runner.addOperatorWithAlias(MathEnum.UUID.getFormula(), MathEnum.GUID.getFormula(), null);
        } catch (Exception e) {
            LOGGER.error("addOperator error", e);
        }
    }
}
